This tutorial assumes you know how to construct LC instances; if not, see the tutorial on Constructing LCs. This tutorial covers the most common methods available in each of the common LC subclasses, such as symbols, declarations, etc.
(Each piece of sample code below is written as if it were a script sitting in
the root folder of this source code repository, and run from there with the
command-line tools node
. If you place your scripts in another folder, you
will need to adjust the path in each import
statement accordingly. If you
have not yet set up a copy of this repository with the appropriate Node.js
version installed, see our GitHub README,
which explains how to do so.)
Symbols have text and sometimes a value
Every Symbol instance gives access to the text of the symbol
itself, using the .text()
method. If the
Symbol you've created can be interpreted as an integer, a real
number (floating point), or a string, you can specify this with an attribute,
as shown below, and then the .value()
method will
respect that attribute and convert the Symbol into a JavaScript
atomic value.
import { LurchSymbol } from './core/src/index.js'
const x = new LurchSymbol( 'x' )
console.log( 'Text of x:', x.text() )
const closeToPi = new LurchSymbol( '3.14159' )
console.log( 'Value of pi:', closeToPi.value() ) // does not work
closeToPi.setAttribute( 'evaluate as', 'real' )
console.log( 'Value of pi:', closeToPi.value() ) // now it works
// Console output:
Text of x: x
Value of pi: undefined
Value of pi: 3.14159
Applications have an operator and operands
The "head" (or first child) of an Application expression is the operator and all the remaining children are the operands. This is because we use prefix notation in putdown and throughout the LDE, in the style of LISP and related languages.
import { LogicConcept } from './core/src/index.js'
const expr1 = LogicConcept.fromPutdown( '(- (* 2 (^ x 2)) 1)' )[0]
console.log( 'Outermost operator:', expr1.operator().toPutdown() )
console.log( 'Its operands:', expr1.operands().map( x => x.toPutdown() ) )
const expr2 = expr1.child( 1 )
console.log( 'Next operator:', expr2.operator().toPutdown() )
console.log( 'Its operands:', expr2.operands().map( x => x.toPutdown() ) )
const expr3 = expr2.child( 2 )
console.log( 'Innermost operator:', expr3.operator().toPutdown() )
console.log( 'Its operands:', expr3.operands().map( x => x.toPutdown() ) )
// Console output:
Outermost operator: -
Its operands: [ '(* 2 (^ x 2))', '1' ]
Next operator: *
Its operands: [ '2', '(^ x 2)' ]
Innermost operator: ^
Its operands: [ 'x', '2' ]
For any expression at all, you can fetch its full list of children, which for an Application is the operator and operands in one list. You can also ask whether an expression is the outermost one in the tree in which it sits (meaning it's inside an Environment, or not inside any parent), and you can fetch the outermost expression surrounding an expression as well.
console.log( 'Children of outer expression:',
expr1.children().map( x => x.toPutdown() ) )
console.log( 'That one is outermost?', expr1.isOutermost() )
console.log( 'Is one of its children outermost?', expr2.isOutermost() )
console.log( 'Fetch outermost from within:',
expr2.getOutermost().toPutdown() )
// Console output:
Children of outer expression: [ '-', '(* 2 (^ x 2))', '1' ]
That one is outermost? true
Is one of its children outermost? false
Fetch outermost from within: (- (* 2 (^ x 2)) 1)
Bindings have a bound variables and a body
You can fetch either the names of the bound variables, as shown below, or the actual bound variable objects (as Symbols) that sit inside the expression, using a different function.
Note that when a binding has multiple symbols bound, you must add parentheses
to clarify where the list of symbols ends, or it will assume just one bound
variable (z
in the example below).
const universal = LogicConcept.fromPutdown( '(forall (x y z) , (P x y z))' )[0]
const binding = universal.child( 1 ) // = x y z , (P x y z)
console.log( 'Quantifier:', universal.child( 0 ).toPutdown() )
console.log( 'Bound variables:', binding.boundSymbolNames() )
console.log( 'Body:', binding.body().toPutdown() )
// Console output:
Quantifier: forall
Bound variables: [ 'x', 'y', 'z' ]
Body: (P x y z)
And the list of children of a binding are the bound variables and the body, in the order they appear in the binding.
console.log( binding.children().map( x => x.toPutdown() ) )
// Console output:
[ 'x', 'y', 'z', '(P x y z)' ]
Declarations have symbols and optionally a body
A Declaration can be of one of only two types: a constant declaration or a variable declaration. There are two static constants in the Declaration class for this purpose, Declaration.Constant and Declaration.Variable, as shown below. You can query the type(), list of symbols(), and body(), although not every declaration has a body.
import { Declaration } from './core/src/index.js'
const decl = LogicConcept.fromPutdown( '[a b c var , (> (+ a b) c)]' )[0]
console.log( 'Declared symbols:', decl.symbols().map( x => x.toPutdown() ) )
console.log( 'Declaration body:', decl.body().toPutdown() )
// Console output:
Declared symbols: [ 'a', 'b', 'c', 'var' ]
Declaration body: (> (+ a b) c)
If there were no body, as in [a b c var]
, then decl.body()
would return
undefined.
Environments have conclusions
As with any LC, you can get the children of an environment with the usual children() method, but you can also ask for its set of conclusions(). (See that link for the definition of "conclusions" in an Environment.) You can also ask whether any LC is a conclusion in one of its ancestors.
const env = LogicConcept.fromPutdown( '{ A :{ B C } { :D E } }' )[0]
console.log( 'Children:', env.children().map( x => x.toPutdown() ) )
console.log( 'Conclusions:', env.conclusions().map( x => x.toPutdown() ) )
const D = env.child( 2 ).child( 0 )
console.log( 'D is a conclusion?', D.isAConclusionIn( env ) )
// Console output:
Children: [ 'A', ':{ B C }', '{ :D E }' ]
Conclusions: [ 'A', 'E' ]
D is a conclusion? false